/*
 *  grfmt_imageio.cpp
 *
 *
 *  Created by Morgan Conbere on 5/17/07.
 *
 */

#include "precomp.hpp"

#ifdef HAVE_IMAGEIO

#include "grfmt_imageio.hpp"

namespace cv {

/////////////////////// ImageIODecoder ///////////////////

ImageIODecoder::ImageIODecoder() {
	imageRef = NULL;
}

ImageIODecoder::~ImageIODecoder() {
	close();
}


void  ImageIODecoder::close() {
	CGImageRelease( imageRef );
	imageRef = NULL;
}


size_t ImageIODecoder::signatureLength() const {
	return 12;
}

bool ImageIODecoder::checkSignature( const string& signature ) const {
	// TODO: implement real signature check
	return true;
}

ImageDecoder ImageIODecoder::newDecoder() const {
	return new ImageIODecoder;
}

bool ImageIODecoder::readHeader() {
	CFURLRef         imageURLRef;
	CGImageSourceRef sourceRef;
	// diciu, if ReadHeader is called twice in a row make sure to release the previously allocated imageRef
	if (imageRef != NULL) {
		CGImageRelease(imageRef);
	}
	imageRef = NULL;

	imageURLRef = CFURLCreateFromFileSystemRepresentation( NULL,
				  (const UInt8*)m_filename.c_str(), m_filename.size(), false );

	sourceRef = CGImageSourceCreateWithURL( imageURLRef, NULL );
	CFRelease( imageURLRef );
	if ( !sourceRef ) {
		return false;
	}

	imageRef = CGImageSourceCreateImageAtIndex( sourceRef, 0, NULL );
	CFRelease( sourceRef );
	if ( !imageRef ) {
		return false;
	}

	m_width = CGImageGetWidth( imageRef );
	m_height = CGImageGetHeight( imageRef );

	CGColorSpaceRef colorSpace = CGImageGetColorSpace( imageRef );
	if ( !colorSpace ) {
		return false;
	}

	m_type = CGColorSpaceGetNumberOfComponents( colorSpace ) > 1 ? CV_8UC3 : CV_8UC1;

	return true;
}


bool  ImageIODecoder::readData( Mat& img ) {
	uchar* data = img.data;
	int step = img.step;
	bool color = img.channels() > 1;
	int bpp; // Bytes per pixel
	int bit_depth = 8;

	// Get Height, Width, and color information
	if ( !readHeader() ) {
		return false;
	}

	CGContextRef     context = NULL; // The bitmap context
	CGColorSpaceRef  colorSpace = NULL;
	uchar*           bitmap = NULL;
	CGImageAlphaInfo alphaInfo;

	// CoreGraphics will take care of converting to grayscale and back as long as the
	// appropriate colorspace is set
	if ( color == CV_LOAD_IMAGE_GRAYSCALE ) {
		colorSpace = CGColorSpaceCreateDeviceGray();
		bpp = 1;
		alphaInfo = kCGImageAlphaNone;
	} else if ( color == CV_LOAD_IMAGE_COLOR ) {
		colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGBLinear );
		bpp = 4; /* CG only has 8 and 32 bit color spaces, so we waste a byte */
		alphaInfo = kCGImageAlphaNoneSkipLast;
	}
	if ( !colorSpace ) {
		return false;
	}

	bitmap = (uchar*)malloc( bpp * m_height * m_width );
	if ( !bitmap ) {
		CGColorSpaceRelease( colorSpace );
		return false;
	}

	context = CGBitmapContextCreate( (void*)bitmap,
									 m_width,        /* width */
									 m_height,       /* height */
									 bit_depth,    /* bit depth */
									 bpp * m_width,  /* bytes per row */
									 colorSpace,     /* color space */
									 alphaInfo);

	CGColorSpaceRelease( colorSpace );
	if ( !context ) {
		free( bitmap );
		return false;
	}

	// Copy the image data into the bitmap region
	CGRect rect = {{0, 0}, {m_width, m_height}};
	CGContextDrawImage( context, rect, imageRef );

	uchar* bitdata = (uchar*)CGBitmapContextGetData( context );
	if ( !bitdata ) {
		free( bitmap);
		CGContextRelease( context );
		return false;
	}

	// Move the bitmap (in RGB) into data (in BGR)
	int bitmapIndex = 0;

	if ( color == CV_LOAD_IMAGE_COLOR ) {
		uchar* base = data;

		for (int y = 0; y < m_height; y++) {
			uchar* line = base + y * step;

			for (int x = 0; x < m_width; x++) {
				// Blue channel
				line[0] = bitdata[bitmapIndex + 2];
				// Green channel
				line[1] = bitdata[bitmapIndex + 1];
				// Red channel
				line[2] = bitdata[bitmapIndex + 0];

				line        += 3;
				bitmapIndex += bpp;
			}
		}
	} else if ( color == CV_LOAD_IMAGE_GRAYSCALE ) {
		for (int y = 0; y < m_height; y++) {
			memcpy (data + y * step, bitmap + y * m_width, m_width);
		}
	}

	free( bitmap );
	CGContextRelease( context );
	return true;
}


/////////////////////// ImageIOEncoder ///////////////////

ImageIOEncoder::ImageIOEncoder() {
	m_description = "Apple ImageIO (*.bmp;*.dib;*.exr;*.jpeg;*.jpg;*.jpe;*.jp2;*.pdf;*.png;*.tiff;*.tif)";
}


ImageIOEncoder::~ImageIOEncoder() {
}


ImageEncoder ImageIOEncoder::newEncoder() const {
	return new ImageIOEncoder;
}

static
CFStringRef  FilenameToUTI( const char* filename ) {
	const char* ext = filename;
	char* ext_buf;
	int i;
	CFStringRef imageUTI = NULL;

	for (;;) {
		const char* temp = strchr( ext + 1, '.' );
		if ( !temp ) { break; }
		ext = temp;
	}

	if (!ext) {
		return NULL;
	}

	ext_buf = (char*)malloc(strlen(ext) + 1);
	for (i = 0; ext[i] != '\0'; i++) {
		ext_buf[i] = (char)tolower(ext[i]);
	}
	ext_buf[i] = '\0';
	ext = ext_buf;

	if ( !strcmp(ext, ".bmp") || !strcmp(ext, ".dib") ) {
		imageUTI = CFSTR( "com.microsoft.bmp" );
	} else if ( !strcmp(ext, ".exr") ) {
		imageUTI = CFSTR( "com.ilm.openexr-image" );
	} else if ( !strcmp(ext, ".jpeg") || !strcmp(ext, ".jpg") || !strcmp(ext, ".jpe") ) {
		imageUTI = CFSTR( "public.jpeg" );
	} else if ( !strcmp(ext, ".jp2") ) {
		imageUTI = CFSTR( "public.jpeg-2000" );
	} else if ( !strcmp(ext, ".pdf") ) {
		imageUTI = CFSTR( "com.adobe.pdf" );
	} else if ( !strcmp(ext, ".png") ) {
		imageUTI = CFSTR( "public.png" );
	} else if ( !strcmp(ext, ".tiff") || !strcmp(ext, ".tif") ) {
		imageUTI = CFSTR( "public.tiff" );
	}

	free(ext_buf);

	return imageUTI;
}


bool  ImageIOEncoder::write( const Mat& img, const vector<int>& params ) {
	int width = img.cols, height = img.rows;
	int _channels = img.channels();
	const uchar* data = img.data;
	int step = img.step;

	// Determine the appropriate UTI based on the filename extension
	CFStringRef imageUTI = FilenameToUTI( m_filename.c_str() );

	// Determine the Bytes Per Pixel
	int bpp = (_channels == 1) ? 1 : 4;

	// Write the data into a bitmap context
	CGContextRef context;
	CGColorSpaceRef colorSpace;
	uchar* bitmapData = NULL;

	if ( bpp == 1 ) {
		colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericGray );
	} else if ( bpp == 4 ) {
		colorSpace = CGColorSpaceCreateWithName( kCGColorSpaceGenericRGBLinear );
	}
	if ( !colorSpace ) {
		return false;
	}

	bitmapData = (uchar*)malloc( bpp * height * width );
	if ( !bitmapData ) {
		CGColorSpaceRelease( colorSpace );
		return false;
	}

	context = CGBitmapContextCreate( bitmapData,
	width,
	height,
	8,
	bpp * width,
	colorSpace,
	(bpp == 1) ? kCGImageAlphaNone :
	kCGImageAlphaNoneSkipLast );
	CGColorSpaceRelease( colorSpace );
	if ( !context ) {
		free( bitmapData );
		return false;
	}

	// Copy pixel information from data into bitmapData
	if (bpp == 4) {
		int           bitmapIndex = 0;
		const uchar* base        = data;

		for (int y = 0; y < height; y++) {
			const uchar* line = base + y * step;

			for (int x = 0; x < width; x++) {
				// Blue channel
				bitmapData[bitmapIndex + 2] = line[0];
				// Green channel
				bitmapData[bitmapIndex + 1] = line[1];
				// Red channel
				bitmapData[bitmapIndex + 0] = line[2];

				line        += 3;
				bitmapIndex += bpp;
			}
		}
	} else if (bpp == 1) {
		for (int y = 0; y < height; y++) {
			memcpy (bitmapData + y * width, data + y * step, width);
		}
	}

	// Turn the bitmap context into an imageRef
	CGImageRef imageRef = CGBitmapContextCreateImage( context );
	CGContextRelease( context );
	if ( !imageRef ) {
		free( bitmapData );
		return false;
	}

	// Write the imageRef to a file based on the UTI
	CFURLRef imageURLRef = CFURLCreateFromFileSystemRepresentation( NULL,
	(const UInt8*)m_filename.c_str(), m_filename.size(), false );
	if ( !imageURLRef ) {
		CGImageRelease( imageRef );
		free( bitmapData );
		return false;
	}

	CGImageDestinationRef destRef = CGImageDestinationCreateWithURL( imageURLRef,
	imageUTI,
	1,
	NULL);
	CFRelease( imageURLRef );
	if ( !destRef ) {
		CGImageRelease( imageRef );
		free( bitmapData );
		fprintf(stderr, "!destRef\n");
		return false;
	}

	CGImageDestinationAddImage(destRef, imageRef, NULL);
	if ( !CGImageDestinationFinalize(destRef) ) {
		fprintf(stderr, "Finalize failed\n");
		return false;
	}

	CFRelease( destRef );
	CGImageRelease( imageRef );
	free( bitmapData );

	return true;
}

}

#endif /* HAVE_IMAGEIO */
